package com.np.rtmpdump1;

import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.projection.MediaProjection;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.Surface;

import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * 编码层，耗时操作，所以让他继承 Thread
 */
public class VideoCodec extends Thread {
    private static final String TAG = "well";
    private MediaProjection mediaProjection;    // 录屏模块
    private MediaCodec mediaCodec;  // 硬编模块
    private ScreenLive screenLive;  // 发送层的引用
    VirtualDisplay virtualDisplay;
    private boolean isLiving;

    public VideoCodec(ScreenLive screenLive) {
        this.screenLive = screenLive;
    }

    public void startLive(MediaProjection mediaProjection) {
        this.mediaProjection = mediaProjection;

        // 拿到图片数据
        try {
            mediaCodec = MediaCodec.createEncoderByType("video/avc");

            // 配置硬编参数，告诉dsp芯片
            MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 540, 960);
            mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); // 输入源颜色格式
            mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);   // 设置帧率
            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 25_000);  // 比特率/码率
            mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2); // I帧间隔，2秒

            // 无需预览，所以第二、三个参数传null
            mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

            Surface surface = mediaCodec.createInputSurface();
            // 创建一个虚拟的展示页，展示到上面的 surface
            virtualDisplay = mediaProjection.createVirtualDisplay("xxx", 540, 960, 1, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 调用线程的 start() 方法，然后执行线程的 run() 方法
        start();
    }

    long timeStamp;
    long startTime;

    @Override
    public void run() {
        isLiving = true;
        mediaCodec.start();
        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        while (isLiving) {

            // 每隔2秒钟，告诉 DSP 芯片，需要主动生成I帧，防止编码器优化后，每2秒编码的不是I帧
            if (System.currentTimeMillis() - timeStamp >= 2000) {

                Bundle bundle = new Bundle();
                bundle.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
                mediaCodec.setParameters(bundle);

                timeStamp = System.currentTimeMillis();
            }

            // mediaProjection 录好的数据
            int index = mediaCodec.dequeueOutputBuffer(bufferInfo, 100000);
            Log.i(TAG, "run: " + index);
            if (index >= 0) {
                ByteBuffer buffer = mediaCodec.getOutputBuffer(index);  // 这里是已经按照配置编码好的数据

                // ByteBuffer 是属于 DSP 芯片的，是只读的。这里需要用一个新的 byte[] 对象去操作
                byte[] outData = new byte[bufferInfo.size];
                buffer.get(outData);

                // 写入 十六进制字符串 到磁盘，仅为了方便查看
                writeHexString(outData);
                // 写入 二进制数组 到磁盘
                writeByteArray(outData);

                if(startTime == 0){
                    startTime = bufferInfo.presentationTimeUs / 1000;
                }

                // 加入到队列
                RTMPPackage rtmpPackage = new RTMPPackage(outData, bufferInfo.presentationTimeUs / 1000 - startTime);
                screenLive.addPackage(rtmpPackage);

                // 释放 ByteBuffer，没有渲染到屏幕，所以 render 传 false
                mediaCodec.releaseOutputBuffer(index, false);
            }
        }
    }

    /**
     * 写入 十六进制字符串 到磁盘（sdcard 文件夹）
     *
     * @param array
     * @return
     */
    public String writeHexString(byte[] array) {
        char[] HEX_CHAR_TABLE = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        StringBuilder sb = new StringBuilder();
        for (byte b : array) {
            sb.append(HEX_CHAR_TABLE[(b & 0xf0) >> 4]);
            sb.append(HEX_CHAR_TABLE[b & 0x0f]);
        }
        Log.i(TAG, "writeHexString: " + sb.toString());
        FileWriter writer = null;
        try {
            // 打开一个写文件器，构造函数中的第二个参数 true 表示以追加的方式写文件
            writer = new FileWriter(Environment.getExternalStorageDirectory() + "/code.txt", true);
            writer.write(sb.toString());
            writer.write('\n');
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }

    /**
     * 写入 二进制数组 到磁盘（sdcard 文件夹）
     *
     * @param array
     */
    public void writeByteArray(byte[] array) {
        FileOutputStream writer = null;
        try {
            // 打开一个文件写入器，构造函数中的第二个参数 true 表示以追加的形式写文件
            writer = new FileOutputStream(Environment.getExternalStorageDirectory() + "/code.h265", true);
            writer.write(array);
            writer.write('\n');
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
